#include <iostream>
#include <stdio.h>
#include "L1_data_cache.h"

#define yesNO(yn) (yn) ? "YES" : "NO"

/**************************
    LRU functions
***************************/

int LRU_way(LRU_4_way lru)
{
    int way = -1;
    if (!lru.bits[0] && !lru.bits[1] && !lru.bits[2])
        way = 0;
    else if ( lru.bits[0] && !lru.bits[3] && !lru.bits[4])
        way = 1;
    else if ( lru.bits[1] &&  lru.bits[3] && !lru.bits[5])
        way = 2;
    else if ( lru.bits[2] &&  lru.bits[4] &&  lru.bits[5])
        way = 3;

    return way;
}

void LRU_update(LRU_4_way& lru, unsigned int way)
{
    if (way<4) {
        switch (way) {
            case 0:
            {
                lru.bits[0] = 1;
                lru.bits[1] = 1;
                lru.bits[2] = 1;
                break;
            }
            case 1:
            {   
                lru.bits[0] = 0;
                lru.bits[3] = 1;
                lru.bits[4] = 1;
                break;
            }
            case 2:
            {
                lru.bits[1] = 0;
                lru.bits[3] = 0;
                lru.bits[5] = 1;
                break;
            }
            case 3:
            {
                lru.bits[2] = 0;
                lru.bits[4] = 0;
                lru.bits[5] = 0;
                break;
            }
        }
    }else{
        // cout << "incorrect way = " << way << endl;;
    }
}

/****************************************************
    L1_data_cache functions
*****************************************************/

// Searches through all the ways of the 
// corresponding index for the data by matching
// valid and tag bits, if not found returns -1
int L1_data_cache::searchWays(unsigned int addr)
{
    unsigned int tag = getTag(addr);
    unsigned int indx=getIndex(addr);
    unsigned int hit = -1;
    
    for (int way = 0; way<4; way++) {
        if (cache[indx][way].valid && cache[indx][way].tag == tag)
        {                   // Data found in cache
            hit = way;      // Set the way as the hit way
            break;
        }
    }
    return hit;             // Return the result
}
 
//  Determines the best possible place for the replacement line. 
//  - Looks for an empty way first. If not found returns LRU.
unsigned int L1_data_cache::findReplacementWay(unsigned int addr){
    
    unsigned int indx = getIndex(addr);
    int emptyWay = -1;
    

    for (int way = 0; way < 4; way++)           // Loop through the 4 ways for an invalid slot 
        if (cache[indx][way].valid == false){   // if not valid     
            emptyWay = way;                     // set empty way to current way
            break;                              // break from the loop
        }
    
    if (emptyWay < 0) return LRU_way(LRU[indx]);// If all slots are occupied return LRU
    else return (unsigned int)emptyWay;         // else return the empty way
}

/***********************************************
    Read & Write
************************************************/
// Writes to L2 everytime
void L1_data_cache::modify(unsigned int addr, unsigned int way, unsigned int word)
{
    if (way > 3){ 
        // cout << "L1_data_cache, modify: way > 3. way = " << way << endl;
        return;
    }

    unsigned int indx = getIndex(addr);
    unsigned int offset = getOffSet(addr);
    
    cache[indx][way].data[offset] = word;           // modify the data in the cache line 
    l2->writeRequest(addr,word);                    // then write to L2 cache    
}

void L1_data_cache::invalidate(unsigned int addr)
{
    // QUESTION: should we let L2 know if L1 evicts 
    //           something
    
    unsigned int indx = getIndex(addr);
    int way = searchWays(addr);
    
    if (way != -1){             // If hit 
        invalidates++;          // bump up invalidate counter
        cache[indx][way].clear();   // clear the cache line
    }
}

void L1_data_cache::evict(unsigned int addr, unsigned int way) //evict?
{
    // QUESTION: should we let L2 know if L1 evicts 
    //           something
    if (way < 4){       // valid way
        unsigned int indx = getIndex(addr);
        if (cache[indx][way].valid){
            // cout << "L1 data | Data eviction for index b" << indx << ".\n";
            cache[indx][way].clear();   // clear the cache line
        }
    }
}

unsigned int L1_data_cache::read(unsigned int addr)
{
	reads++;                           // increment read counter
    int hit_way = searchWays(addr);    // search the data in the cache set
    unsigned int indx = getIndex(addr);
    unsigned int offset = getOffSet(addr);
    if (hit_way != -1){                         // if hit        
        hits++;                                 // increment read counter
        // cout<<"L1 Data Hit.\n";                 // report hit
        LRU_update(LRU[indx], hit_way);         // Update LRU
        return cache[indx][hit_way].data[offset]; 
    }else{                                      // else 
        misses++;                               // increment miss counter
        unsigned int lru = findReplacementWay(addr);
        l2->readRequest(addr, cache[indx][lru].data);   // Read from L2 cache
        if (cache[indx][lru].valid)                     // if line is valid
            evict(addr,lru);                            // evict the line        
        cache[indx][lru].setAddress(addr);              // Fill up the new cache line
        LRU_update(LRU[indx], lru);                 // Update LRU
        return cache[indx][lru].data[offset];
    }
}

void L1_data_cache::write(unsigned int addr)
{
	writes++;                                   // Report hit
    int hit_way = searchWays(addr);             // Check to see if we have a hit
    unsigned int indx = getIndex(addr);         // Get the index 
    
    if (hit_way != -1){                         // hit          
        hits++;                                 // report hit
        modify(addr, hit_way, addr);            // datum in cache so modify
        LRU_update(LRU[indx], hit_way);         // Update LRU 
        
    }else{                                      // miss
        misses++;                               // report miss
        unsigned int lru = findReplacementWay(addr);
        l2->readRequest(addr, cache[indx][lru].data);
        if (cache[indx][lru].valid)             // if line is valid
            evict(addr,lru);                    // evict the line        
        cache[indx][lru].setAddress(addr);      // Fill up the new cache line
        modify(addr, lru, addr);                // then modify the bytes
        LRU_update(LRU[indx], lru);             // Update LRU 
    }    
}

void L1_data_cache::PrintContents()				//what is fprint, "a", 
{
    FILE *fod = fopen(filepath, "a");  //or "a" for append?
    if (fod == NULL) return;
	print_req++;
    fprintf(fod,"\nPrint Request: %ld\n",print_req);
    printf("\n********** Data Cache **********\nPrint Request: %ld\n",print_req);
	for (int indx = 0; indx < nSets; indx++)						//for all possible indexes, if way is valid - 
	{
        bool valid_line = false;
       for (int way = 0; way < nWays; way++)		//for each of these valid ways, print contents
      {
            if (cache[indx][way].valid){
                valid_line = true;
                fprintf(fod,"INDEX: 0x%-8x ",indx);
                fprintf(fod,"Way %-8d Tag: 0x%-8x  Address: 0x%-8x\n", way, cache[indx][way].tag, cache[indx][way].address);                     
                printf("INDEX: 0x%-8x ",indx);
                printf("Way %-8d Tag: 0x%-8x  Address: 0x%-8x\n", way, cache[indx][way].tag, cache[indx][way].address);                     

            }
      }
        if (valid_line){
            fprintf(fod, "LRU: %d\n",LRU_way(LRU[indx]));
            printf("LRU: %d\n",LRU_way(LRU[indx]));
        }

    }
    fprintf(fod,"\n");
    fflush(fod);
    fclose(fod);
}

void L1_data_cache::ResetState()
{
  int index;
  int way;
  for (index = 0; index < nSets; index++)
    for (way = 0; way < nWays; way++)
        cache[index][way].valid = false;
}
